package resml

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

// HTTP 资源服务器的请求处理器
type HttpResourceServerHandler struct {
	prefix     string  // 路径前缀
	storage    Storage // 文件名目录
	sendwriter func(w io.Writer) io.Writer
}

// Http 资源操作
type HttpResourceOperation struct {
	Op   string `json:"op"`   // 操作
	Name string `json:"name"` // 资源名
}

// 创建 HTTP 资源服务器的请求处理器。
// prefix: 路径前缀如果指定，则在映射到存储系统时删除前缀。
// 例如 GET /files/path/to/file.txt 时，存储系统的路径为 /path/to/file.txt
func NewHttpResourceServerHandler(prefix string, storage Storage) *HttpResourceServerHandler {
	return &HttpResourceServerHandler{prefix: prefix, storage: storage}
}

func (h *HttpResourceServerHandler) WithSendWriter(fn func(w io.Writer) io.Writer) *HttpResourceServerHandler {
	h.sendwriter = fn
	return h
}

// 处理 HTTP 请求
func (h *HttpResourceServerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := strings.TrimSpace(r.URL.Path)
	if !strings.HasPrefix(path, "/") {
		path = "/" + path
	}
	if h.prefix != "" && strings.HasPrefix(path, h.prefix) {
		path = strings.TrimPrefix(path, h.prefix)
		if !strings.HasPrefix(path, "/") {
			path = "/" + path
		}
	}
	switch r.Method {
	case http.MethodGet: // 资源获取
		h.get(path, w)
	case http.MethodHead: // 资源元数据
		h.head(path, w)
	case http.MethodDelete: // 资源删除
		h.delete(path, w)
	case http.MethodPut: // 资源保存
		h.save(path, r, w)
	case http.MethodPost: // 上传或重命名
		h.uploadOrRename(path, r, w)
	default:
		http.Error(w, "method not allow", http.StatusMethodNotAllowed)
	}
}

func (h *HttpResourceServerHandler) get(path string, w http.ResponseWriter) {
	if strings.HasSuffix(path, "/") { // 目录，返回 list。
		if si, err := h.storage.List(path); err != nil {
			h.check(err, w)
		} else {
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusOK)
			json.NewEncoder(w).Encode(si)
		}
		return
	} // 目录返回结束，下面是返回文件
	rc, meta, err := h.storage.Get(path)
	if h.check(err, w) {
		return
	}
	defer rc.Close()
	MetaSetToHttpHeader(meta, w.Header(), false)
	w.WriteHeader(http.StatusOK)
	if h.sendwriter != nil {
		io.Copy(h.sendwriter(w), rc)
		return
	}
	io.Copy(w, rc)
}

func (h *HttpResourceServerHandler) head(path string, w http.ResponseWriter) {
	if strings.HasSuffix(path, "/") { // 目录，不返回 meta。
		w.WriteHeader(http.StatusOK)
		return
	}
	meta, err := h.storage.Meta(path)
	if h.check(err, w) {
		return
	}
	MetaSetToHttpHeader(meta, w.Header(), false)
	w.WriteHeader(http.StatusOK)
}

func (h *HttpResourceServerHandler) delete(path string, w http.ResponseWriter) {
	err := h.storage.Remove(path)
	if h.check(err, w) {
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *HttpResourceServerHandler) rename(path, newname string, w http.ResponseWriter) {
	if h.check(h.storage.Rename(path, newname), w) {
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (h *HttpResourceServerHandler) uploadOrRename(path string, r *http.Request, w http.ResponseWriter) {
	if r.Body == nil {
		h.badRequestError(w, "bad request", fmt.Errorf("nil request body %s %s", r.Method, r.URL))
		return
	}
	mr, err := r.MultipartReader()
	if err != nil {
		if err == http.ErrNotMultipart || errors.Is(err, http.ErrNotMultipart) {
			if strings.Contains(strings.ToLower(r.Header.Get("Content-Type")), "application/json") {
				body, err := io.ReadAll(r.Body)
				if err != nil {
					h.internalError(w, err)
					return
				}
				op := &HttpResourceOperation{}
				if err = json.Unmarshal(body, op); err != nil {
					h.badRequestError(w, "bad request, invalid json", err)
					return
				}
				if op.Op == "rename" {
					h.rename(path, op.Name, w)
					return
				}
				// 不是重命名的操作，将请求体填回去，作为普通文件处理
				r.Body = io.NopCloser(bytes.NewBuffer(body))
			}
			h.save(path, r, w) // 不是 Form-Data，或，使用 save 方法保存资源
			return
		}
		h.badRequestError(w, "bad request", err)
		return
	}
	for {
		part, err := mr.NextPart()
		if err == io.EOF {
			break
		} else if err != nil {
			h.badRequestError(w, "bad request, can not read part", err)
			return
		}
		if part.FormName() != "file" {
			continue
		}
		defer part.Close()
		h.saveres(path, w, http.Header(part.Header), part)
		return
	}
	h.badRequestError(w, "bad request, no file part", nil)
}

func (h *HttpResourceServerHandler) save(path string, r *http.Request, w http.ResponseWriter) {
	if r.Body == nil {
		h.badRequestError(w, "empty body", nil)
		return
	}
	defer r.Body.Close()
	h.saveres(path, w, r.Header, r.Body)
}

func (h *HttpResourceServerHandler) saveres(path string, w http.ResponseWriter, hdr http.Header, r io.Reader) {
	meta, err := MetaOfHttpHeader(hdr)
	if err != nil {
		h.badRequestError(w, "invalid request header", err)
		return
	}
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")
	w.WriteHeader(http.StatusOK)
	pr := NewProgressReader(r, meta.ContentLength())
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		ticker := time.NewTicker(time.Second)
		defer ticker.Stop()
		timout := 2 * time.Minute
		since := time.Now()
		total, last := pr.total, pr.Current()
		for range ticker.C {
			if cur := pr.Current(); cur >= total {
				h.sseWrite(w, "percent", fmt.Sprintf("%.2f%%", pr.Percent()))
				return
			} else if cur > last {
				last = cur
				since = time.Now()
				h.sseWrite(w, "percent", fmt.Sprintf("%.2f%%", pr.Percent()))
			} else if time.Since(since) > timout {
				h.sseWrite(w, "error", "timeout")
				return
			}
		}
	}()
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err := h.storage.Save(path, meta, pr); err != nil {
			h.sseWrite(w, "error", "save error")
			log.Printf("Storage Save Error, path=%s, error: %v", path, err)
		}
		h.sseWrite(w, "done", "save error")
	}()
	wg.Wait()
}

func (h *HttpResourceServerHandler) sseWrite(w io.Writer, tag, msg string) {
	t := time.Now().Format(time.RFC3339)
	fmt.Fprintf(w, "data: [%s] [%s] %s\n\n", t, strings.ToUpper(strings.TrimSpace(tag)), msg)
}

func (h *HttpResourceServerHandler) check(err error, w http.ResponseWriter) bool {
	if err == nil {
		return false
	}
	if os.IsNotExist(err) {
		h.notfound(w)
		return true
	} else if os.IsPermission(err) {
		h.forbidden(w)
		return true
	}
	h.internalError(w, err)
	return true
}

func (h *HttpResourceServerHandler) notfound(w http.ResponseWriter) {
	http.Error(w, "not found", http.StatusNotFound)
}

func (h *HttpResourceServerHandler) forbidden(w http.ResponseWriter) {
	http.Error(w, "Forbidden", http.StatusForbidden)
}

func (h *HttpResourceServerHandler) badRequestError(w http.ResponseWriter, msg string, err error) {
	log.Print(err)
	if msg == "" {
		http.Error(w, "bad request error", http.StatusBadRequest)
		return
	}
	http.Error(w, "bad request error: "+msg, http.StatusBadRequest)
}

func (h *HttpResourceServerHandler) internalError(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, "internal error", http.StatusInternalServerError)
}
